﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Text;
using System.Windows.Forms;

namespace production
{
    class RatioMaterial : data.productionDS.prodRatioMaterialDataTable
    {
        DataView myView = null;
        public RatioMaterial(tempDS.productionPlanDataTable planDataTbl)
        {
            this.DefaultView.Sort = this.subProductCodeColumn.ColumnName + "," + this.subTaskCodeColumn.ColumnName;
            this.myView = new DataView(this);
            this.myView.Sort = this.productCodeColumn.ColumnName + "," + this.taskCodeColumn.ColumnName + "," +
                               this.subProductCodeColumn.ColumnName + "," + this.subTaskCodeColumn.ColumnName;
            LoadRatio(planDataTbl);
        }
        private void LoadRatio(tempDS.productionPlanDataTable planDataTbl)
        {
            data.productionDS.prodRatioMaterialDataTable dataTbl = new data.productionDS.prodRatioMaterialDataTable();
            data.productionDS.prodRatioMaterialRow ratioRow;
            for (int idx1 = 0; idx1 < planDataTbl.Count; idx1++)
            {
                dataTbl.Clear();
                DataAccess.DataLibs.LoadRatioHaveSubCode(dataTbl, planDataTbl[idx1].itemCode, planDataTbl[idx1].taskCode);
                for (int idx2 = 0; idx2 < dataTbl.Count; idx2++)
                {
                    ratioRow = this.NewprodRatioMaterialRow();
                    ratioRow.ItemArray = dataTbl[idx2].ItemArray;
                    ratioRow.qty *= planDataTbl[idx1].planQty;
                    this.AddprodRatioMaterialRow(ratioRow);
                }
            }
        }

        public DataRowView[] FindBySub(string subProductCode, string subTaskCode)
        {
            return this.DefaultView.FindRows(new object[] { subProductCode, subTaskCode });
        }
        public DataRowView[] FindByCodeAndSub(string productCode, string taskCode, string subProductCode, string subTaskCode)
        {
            return this.myView.FindRows(new object[] { productCode, taskCode, subProductCode, subTaskCode });
        }
    }
    public class Material : tempDS.materialDataTable
    {
        public new void Clear()
        {
            base.Clear();

            if (this.myRatioMaterial == null) return;
            this.myRatioMaterial.Clear();
            this.myRatioMaterial = null;
        }

        //======================================================
        // - Group item code into code used in production 
        // - Split compound item into it's components 
        //======================================================
        public void Validate(tempDS.productionPlanDataTable planDataTbl)
        {
            tempDS.materialDataTable tmpTbl = (tempDS.materialDataTable)this.Copy();
            this.Clear();

            data.masterDS.ItemRow itemRow;
            tempDS.materialRow materialRow;
            for (int idx = 0; idx < tmpTbl.Count; idx++)
            {
                itemRow = DataAccess.DataCache.FindITEM(tmpTbl[idx].itemCode);
                //Change item code to code used in production if possible
                tmpTbl[idx].itemCode = (itemRow.IsprodItemCodeNull() ? tmpTbl[idx].itemCode : itemRow.prodItemCode);
                //If item is a compound item, split it into components as specified in the template 
                if (itemRow.IsprodTaskCodeNull() == false)
                {
                    if (Split_IntoComponents(tmpTbl[idx], itemRow.prodTaskCode, planDataTbl, this))
                        continue;
                }
                materialRow = this.Add(tmpTbl[idx].itemCode, tmpTbl[idx].toProductCode, tmpTbl[idx].toTaskCode);
                materialRow.qty += tmpTbl[idx].qty;
                materialRow.amt += tmpTbl[idx].amt;
            }
        }

        // Split compound item into components specified in the product template 
        RatioMaterial myRatioMaterial = null;
        private bool Split_IntoComponents(tempDS.materialRow materialRow, string taskCode,tempDS.productionPlanDataTable planDataTbl,
                                          tempDS.materialDataTable dataTbl)
        {
            if (myRatioMaterial == null)
            {
                this.myRatioMaterial = new RatioMaterial(planDataTbl);
            }
            DataRowView[] foundRows;

            //If know product, split into components of the product only
            if (materialRow.toProductCode.Trim() != "" && materialRow.toTaskCode.Trim() != "")
                foundRows = this.myRatioMaterial.FindByCodeAndSub(materialRow.toProductCode, materialRow.toTaskCode, materialRow.itemCode, taskCode);
            else
            {
                //NOT know product, split into components in product ratio
                foundRows = this.myRatioMaterial.FindBySub(materialRow.itemCode, taskCode);
            }
            
            if (foundRows.Length == 0)  return false;
            
            decimal totalWeightAmt = 0, totalWeightQty = 0;
            decimal remainDistAmt, remainDistQty, amt, qty, price;
            data.productionDS.prodRatioMaterialRow ratioRow;
            for (int idx = 0; idx < foundRows.Length; idx++)
            {
                ratioRow = (data.productionDS.prodRatioMaterialRow)foundRows[idx].Row;
                price = ProductionLibs.GetPlanPrice(ratioRow.itemCode);
                totalWeightAmt += ratioRow.qty * (price == 0 ? 1 : price);
                totalWeightQty += ratioRow.qty;
            }
            if (totalWeightAmt == 0)
            {
                return false;
            }
            remainDistQty = materialRow.qty;
            remainDistAmt = materialRow.amt;

            tempDS.materialRow newMaterialRow, lastMaterialRow;
            lastMaterialRow = null;
            for (int idx = 0; idx < foundRows.Length; idx++)
            {
                ratioRow = (data.productionDS.prodRatioMaterialRow)foundRows[idx].Row;
                price = ProductionLibs.GetPlanPrice(ratioRow.itemCode);
                newMaterialRow = production.DataLibs.Add_Material(ratioRow.itemCode,ratioRow.productCode,ratioRow.taskCode, dataTbl);
                qty = Math.Round(materialRow.qty * ratioRow.qty  / totalWeightQty, Settings.sysPrecisionQty);
                amt = Math.Round(materialRow.amt * ratioRow.qty * (price == 0 ? 1 : price) / totalWeightAmt, Settings.sysPrecisionAmt);
                newMaterialRow.qty += qty;
                newMaterialRow.amt += amt;
                remainDistQty -= qty;
                remainDistAmt -= amt;
                lastMaterialRow = newMaterialRow;
            }
            if (lastMaterialRow != null)
            {
                lastMaterialRow.qty += remainDistQty;
                lastMaterialRow.amt += remainDistAmt;
            }
            return true;
        }

        public tempDS.materialRow Add(string itemCode,string productCode,string taskCode)
        {
            return DataLibs.Add_Material(itemCode,productCode,taskCode, this);
        }

        public void SaveData(DateTime onDate)
        {
            decimal qty, amt;
            data.productionDS.prodIncompleteMaterialRow incompleteMaterialRow;
            data.productionDS.prodIncompleteMaterialDataTable incompleteMaterialTbl = new data.productionDS.prodIncompleteMaterialDataTable();
            for (int idx = 0; idx < this.Count; idx++)
            {
                qty = this[idx].qty - this[idx].completeQty;
                amt = this[idx].amt - this[idx].completeAmt + this[idx].differentialAmt;
                if (qty == 0 && amt == 0) continue;

                incompleteMaterialRow = incompleteMaterialTbl.NewprodIncompleteMaterialRow();
                DataAccess.DataLibs.InitData(incompleteMaterialRow);
                incompleteMaterialRow.onDate = onDate;
                incompleteMaterialRow.itemCode = this[idx].itemCode;
                if (this[idx].toProductCode.Trim()!="") incompleteMaterialRow.productCode = this[idx].toProductCode;
                if (this[idx].toTaskCode.Trim() != "") incompleteMaterialRow.taskCode = this[idx].toTaskCode;
                incompleteMaterialTbl.AddprodIncompleteMaterialRow(incompleteMaterialRow);
                incompleteMaterialRow.qty += qty; ;
                incompleteMaterialRow.amt += amt;
            }
            DataAccess.DataLibs.UpdateData(incompleteMaterialTbl);
        }
    }
    public class Expense : tempDS.expenseDataTable
    {
        public tempDS.expenseRow Add(string accountNo, string productCode, string taskCode)
        {
            return DataLibs.Add_Expense(accountNo, productCode, taskCode, this);
        }
        public void SaveData(DateTime onDate)
        {
            decimal amt;
            data.productionDS.prodIncompleteExpenseRow incompleteRow;
            data.productionDS.prodIncompleteExpenseDataTable incompleteTbl = new data.productionDS.prodIncompleteExpenseDataTable();
            for (int idx = 0; idx < this.Count; idx++)
            {
                amt = this[idx].amt - this[idx].completeAmt; 
                if (amt == 0) continue;

                incompleteRow = incompleteTbl.NewprodIncompleteExpenseRow();
                DataAccess.DataLibs.InitData(incompleteRow);
                incompleteRow.onDate = onDate;
                incompleteRow.accountNo = this[idx].accountNo;
                if (this[idx].toProductCode.Trim() != "") incompleteRow.productCode = this[idx].toProductCode;
                if (this[idx].toTaskCode.Trim() != "") incompleteRow.taskCode = this[idx].toTaskCode;
                incompleteTbl.AddprodIncompleteExpenseRow(incompleteRow);
                incompleteRow.amt += amt;
            }
            DataAccess.DataLibs.UpdateData(incompleteTbl);
        }
    }
    public class ProductionPlan : tempDS.productionPlanDataTable
    {
        public tempDS.productionPlanRow Find(string productCode, string taskCode)
        {
            return this.FindBytaskCodeitemCode(taskCode, productCode);
        }
        public tempDS.productionPlanRow Add(string productCode, string taskCode)
        {
            return DataLibs.AddRow_ProductionPlan(productCode, taskCode, this);
        }
    }

    public class ProductionData : tempDS.productionDataDataTable
    {
        private decimal _totalWeight = common.Consts.constNullDecimal;
        private decimal TotalWeight
        {
            get
            {
                if (_totalWeight == common.Consts.constNullDecimal)
                {
                    decimal price;
                    _totalWeight = 0;
                    for (int idx1 = 0; idx1 < this.Count; idx1++)
                    {
                        price = ProductionLibs.GetPlanPrice(this[idx1].productCode);
                        _totalWeight += this[idx1].planQty * (price == 0 ? 1 : price);
                    }
                }
                return _totalWeight;
            }
        }

        public new void Clear()
        {
            base.Clear();
            _totalWeight = common.Consts.constNullDecimal;
        }
        public tempDS.productionDataRow Find(string productCode, string taskCode)
        {
            return this.FindBytaskCodeproductCode(taskCode, productCode);
        }
        public tempDS.productionDataRow Add(string productCode,string taskCode)
        {
            return DataLibs.AddRow_ProductionData(productCode, taskCode, this);
        }

        public bool DistributeExpense(tempDS.expenseRow expenseRow, ProductionRatioExpense toTbl)
        {
            if (this.TotalWeight == 0 && this.Count != 0) return false;

            tempDS.productionRatioExpenseRow ratioRow = null, lastRatioRow = null;
            decimal remainOutAmt, amt, price;
            remainOutAmt = expenseRow.amt;
            for (int idx = 0; idx < this.Count; idx++)
            {
                price = ProductionLibs.GetPlanPrice(this[idx].productCode);
                ratioRow = toTbl.Add(this[idx].productCode, this[idx].taskCode, expenseRow.accountNo);
                amt = Math.Round(this[idx].planQty * (price == 0 ? 1 : price) / this.TotalWeight * expenseRow.amt, Settings.sysPrecisionAmt);
                ratioRow.productionAmt += amt;
                remainOutAmt -= amt;
                lastRatioRow = ratioRow;
            }
            if (lastRatioRow != null)
            {
                lastRatioRow.productionAmt += remainOutAmt;
            }
            return true;
        }
        public bool DistributeMaterial(tempDS.materialRow materialRow, ProductionRatioMaterial toTbl)
        {
            if (this.TotalWeight == 0 && this.Count != 0) return false;
            tempDS.productionRatioMaterialRow ratioRow = null, lastRatioRow = null;
            decimal remainOutQty, remainOutAmt, qty, amt, price;
            remainOutQty = materialRow.qty;
            remainOutAmt = materialRow.amt;
            for (int idx = 0; idx < this.Count; idx++)
            {
                price = ProductionLibs.GetPlanPrice(this[idx].productCode);
                ratioRow = toTbl.Add(materialRow.itemCode, this[idx].productCode, this[idx].taskCode);
                qty = Math.Round(materialRow.qty * this[idx].planQty * (price == 0 ? 1 : price) / this.TotalWeight, Settings.sysPrecisionQty);
                amt = Math.Round(materialRow.amt * this[idx].planQty * (price == 0 ? 1 : price) / this.TotalWeight, Settings.sysPrecisionQty);
                ratioRow.productionQty += qty;
                ratioRow.productionAmt += amt;
                remainOutQty -= qty;
                remainOutAmt -= amt;
                lastRatioRow = ratioRow;
            }
            if (lastRatioRow != null)
            {
                lastRatioRow.productionQty += remainOutQty;
                lastRatioRow.productionAmt += remainOutAmt;
            }
            return true;
        }

        public class EstimateResult
        {
            public decimal completeQty = 0, completeAmt = 0;
        }
        public EstimateResult EstimateComplete_Material(string productCode, string taskCode, decimal qty, decimal amt,bool withUpdate)
        {
            if (qty == 0 && amt == 0) return null;
            tempDS.productionDataRow productionDataRow;
            productionDataRow = this.Find(productCode, taskCode);
            if (productionDataRow == null || productionDataRow.planQty == 0)  return null;

            EstimateResult estimateResult = new EstimateResult();
            estimateResult.completeQty = Math.Round(qty * productionDataRow.produceQty / productionDataRow.planQty, Settings.sysPrecisionQty);
            estimateResult.completeAmt = Math.Round(amt * productionDataRow.produceQty / productionDataRow.planQty, Settings.sysPrecisionAmt);
            if (withUpdate) productionDataRow.produceMaterialAmt += estimateResult.completeAmt;
            return estimateResult;
        }
        public EstimateResult EstimateComplete_Expense(string productCode, string taskCode, decimal amt, bool withUpdate)
        {
            if (amt == 0) return null;
            tempDS.productionDataRow productionDataRow;
            productionDataRow = this.Find(productCode, taskCode);
            if (productionDataRow == null || productionDataRow.planQty == 0) return null;

            EstimateResult estimateResult = new EstimateResult();
            estimateResult.completeAmt = Math.Round(amt * productionDataRow.produceQty / productionDataRow.planQty, Settings.sysPrecisionAmt);
            if (withUpdate) productionDataRow.produceExpenseAmt += estimateResult.completeAmt;
            return estimateResult;
        }

        public void SaveData(DateTime onDate)
        {
            //Planning
            data.productionDS.prodIncompletePlanRow incompletePlanRow;
            data.productionDS.prodIncompletePlanDataTable incompletePlanTbl = new data.productionDS.prodIncompletePlanDataTable();
            for (int idx = 0; idx < this.Count; idx++)
            {
                if (this[idx].planQty==0 && this[idx].produceQty == 0) continue;
                incompletePlanRow = incompletePlanTbl.FindByonDatetaskCodeitemCode(onDate, this[idx].taskCode, this[idx].productCode);
                if (incompletePlanRow == null)
                {
                    incompletePlanRow = incompletePlanTbl.NewprodIncompletePlanRow();
                    DataAccess.DataLibs.InitData(incompletePlanRow);
                    incompletePlanRow.onDate = onDate;
                    incompletePlanRow.itemCode = this[idx].productCode;
                    incompletePlanRow.taskCode = this[idx].taskCode;
                    incompletePlanTbl.AddprodIncompletePlanRow(incompletePlanRow);
                }
                incompletePlanRow.planQty += this[idx].planQty;
                incompletePlanRow.completeQty += this[idx].produceQty;
            }
            DataAccess.DataLibs.UpdateData(incompletePlanTbl);
        }

        public void CheckError(ProductionErrors toError)
        {
            for (int idx = 0; idx < this.Count; idx++)
            {
                if (this[idx].RowState == System.Data.DataRowState.Deleted) continue;
                if (this[idx].planQty < this[idx].produceQty)
                {
                    toError.Add(((byte)ProductionErrors.Codes.ProductPlanQtyInvalid).ToString() +"-"+this[idx].taskCode+"-"+this[idx].productCode,
                                ProductionErrors.Codes.ProductPlanQtyInvalid.ToString(), ProductionErrors.GetText(ProductionErrors.Codes.ProductPlanQtyInvalid),
                                "(" + this[idx].taskCode + "," + this[idx].productCode + ") = (" + this[idx].planQty.ToString(CommonTypes.Settings.sysMaskQty) + "," + this[idx].produceQty.ToString(CommonTypes.Settings.sysMaskQty) + ")");
                }
            }
        }
    }
    public class ProductionRatioMaterial : tempDS.productionRatioMaterialDataTable
    {
        public ProductionRatioMaterial()
        {
            this.DefaultView.Sort = this.productCodeColumn.ColumnName + "," + this.taskCodeColumn.ColumnName;
        }

        public DataRowView[] Find(string productCode, string taskCode)
        {
            return this.DefaultView.FindRows(new object[] { productCode, taskCode });
        }
        public tempDS.productionRatioMaterialRow Find(string itemCode, string productCode, string taskCode)
        {
            return this.FindByproductCodeitemCodetaskCode(productCode, itemCode, taskCode);
        }
        public tempDS.productionRatioMaterialRow Add(string itemCode, string productCode, string taskCode)
        {
            return DataLibs.AddRow_ProdMaterialRatio(productCode, taskCode, itemCode, this);
        }

        public void LoadRatio(string productCode, string taskCode)
        {
            //If existed. do not load 
            if (this.Find(productCode, taskCode).Length > 0) return;

            data.productionDS.prodRatioMaterialDataTable materialTbl = new data.productionDS.prodRatioMaterialDataTable();
            DataAccess.DataLibs.LoadData(materialTbl, productCode, taskCode);
            tempDS.productionRatioMaterialRow materialRow;
            for (int idx = 0; idx < materialTbl.Count; idx++)
            {
                materialRow = this.Add(materialTbl[idx].itemCode, productCode, taskCode);
                materialRow.planRatio += materialTbl[idx].qty;
            }
        }
        public void SaveData(DateTime onDate)
        {
            data.productionDS.prodMaterialRow materialRow;
            data.productionDS.prodMaterialDataTable materialTbl = new data.productionDS.prodMaterialDataTable();
            for (int idx = 0; idx < this.Count; idx++)
            {
                if (this[idx].planQty == 0 &&
                    this[idx].productionQty == 0) continue;

                materialRow = materialTbl.FindByonDateproductCodetaskCodeitemCode(onDate, this[idx].productCode, this[idx].taskCode, this[idx].itemCode);
                if (materialRow == null)
                {
                    materialRow = materialTbl.NewprodMaterialRow();
                    DataAccess.DataLibs.InitData(materialRow);
                    materialRow.onDate = onDate;
                    materialRow.productCode = this[idx].productCode;
                    materialRow.taskCode = this[idx].taskCode;
                    materialRow.itemCode = this[idx].itemCode;
                    materialTbl.AddprodMaterialRow(materialRow);
                }
                materialRow.planQty += this[idx].planQty;
                materialRow.productionQty += this[idx].productionQty;
                materialRow.productionAmt += this[idx].productionAmt;
                materialRow.completeAmt += this[idx].completeAmt;
                materialRow.completeQty += this[idx].completeQty;

            }
            DataAccess.DataLibs.UpdateData(materialTbl);
        }

        public bool Distribute(tempDS.materialRow row, DataRowView[] toRows, ProductionRatioMaterial toTbl)
        {
            if (row.qty == 0) return true;

            tempDS.productionRatioMaterialRow newRatioRow, ratioRow, lastRatioRow = null;

            decimal totalQty = 0;
            for (int idx = 0; idx < toRows.Length; idx++)
            {
                ratioRow = (tempDS.productionRatioMaterialRow)toRows[idx].Row;
                totalQty += ratioRow.planQty;
            }
            if (totalQty == 0) return false;

            decimal remainOutQty = row.qty;
            decimal remainOutAmt = row.amt;
            for (int idx = 0; idx < toRows.Length; idx++)
            {
                ratioRow = (tempDS.productionRatioMaterialRow)toRows[idx].Row;
                if (ratioRow.planQty == 0) continue;

                newRatioRow = toTbl.Add(ratioRow.itemCode, ratioRow.productCode, ratioRow.taskCode);
                newRatioRow.productionQty = Math.Round(ratioRow.planQty / totalQty * row.qty, Settings.sysPrecisionQty);
                newRatioRow.productionAmt = Math.Round(ratioRow.planQty / totalQty * row.amt, Settings.sysPrecisionAmt);

                remainOutQty -= newRatioRow.productionQty;
                remainOutAmt -= newRatioRow.productionAmt;
                lastRatioRow = newRatioRow;
            }
            if (lastRatioRow != null)
            {
                lastRatioRow.productionQty += remainOutQty;
                lastRatioRow.productionAmt += remainOutAmt;
            }
            return true;
        }

        public new ProductionRatioMaterial Copy()
        {
            ProductionRatioMaterial newData = new ProductionRatioMaterial();
            tempDS.productionRatioMaterialDataTable tbl = (tempDS.productionRatioMaterialDataTable)base.Copy();
            for (int idx = 0; idx < tbl.Count; idx++) newData.ImportRow(tbl[idx]);
            return newData;
        }
    }
    public class ProductionRatioExpense : tempDS.productionRatioExpenseDataTable
    {
        public ProductionRatioExpense()
        {
            this.DefaultView.Sort = this.productCodeColumn.ColumnName + "," + this.taskCodeColumn.ColumnName;
        }
        public DataRowView[] Find(string productCode, string taskCode)
        {
            return this.DefaultView.FindRows(new object[] { productCode, taskCode });
        }
        public void LoadRatio(string productCode, string taskCode)
        {
            //If existed. do not load 
            if (this.Find(productCode, taskCode).Length > 0) return;
            data.productionDS.prodRatioExpenseDataTable expenseTbl = new data.productionDS.prodRatioExpenseDataTable();
            DataAccess.DataLibs.LoadData(expenseTbl, productCode, taskCode);
            tempDS.productionRatioExpenseRow expenseRow;
            for (int idx = 0; idx < expenseTbl.Count; idx++)
            {
                expenseRow = this.Add(productCode, taskCode, expenseTbl[idx].accountNo);
                expenseRow.planRatio += expenseTbl[idx].amt;
            }
        }
        public tempDS.productionRatioExpenseRow Add(string productCode, string taskCode, string accountNo)
        {
            return DataLibs.AddRow_ProdExpenseRatio(productCode, taskCode, accountNo, this);
        }
        public void SaveData(DateTime onDate)
        {
            data.productionDS.prodExpenseRow expenseRow;
            data.productionDS.prodExpenseDataTable expenseTbl = new data.productionDS.prodExpenseDataTable();
            for (int idx = 0; idx < this.Count; idx++)
            {
                if (this[idx].planAmt == 0 &&
                    this[idx].productionAmt == 0) continue;

                expenseRow = expenseTbl.FindByonDatetaskCodeproductCodeaccountNo(onDate, this[idx].taskCode, this[idx].productCode, this[idx].accountNo);
                if (expenseRow == null)
                {
                    expenseRow = expenseTbl.NewprodExpenseRow();
                    DataAccess.DataLibs.InitData(expenseRow);
                    expenseRow.onDate = onDate;
                    expenseRow.taskCode = this[idx].taskCode;
                    expenseRow.productCode = this[idx].productCode;
                    expenseRow.accountNo = this[idx].accountNo;
                    expenseTbl.AddprodExpenseRow(expenseRow);
                }
                expenseRow.planAmt += this[idx].planAmt;
                expenseRow.productionAmt += this[idx].productionAmt;
                expenseRow.completeAmt += this[idx].completeAmt;
            }
            DataAccess.DataLibs.UpdateData(expenseTbl);
        }

        public bool Distribute(tempDS.expenseRow row, DataRowView[] toRows, ProductionRatioExpense toTbl)
        {
            if (row.amt == 0) return true;

            tempDS.productionRatioExpenseRow newRatioRow, ratioRow, lastRatioRow = null;

            decimal totalAmt = 0;
            for (int idx = 0; idx < toRows.Length; idx++)
            {
                ratioRow = (tempDS.productionRatioExpenseRow)toRows[idx].Row;
                totalAmt += ratioRow.planAmt;
            }
            if (totalAmt == 0) return false;

            decimal remainOutAmt = row.amt;
            for (int idx = 0; idx < toRows.Length; idx++)
            {
                ratioRow = (tempDS.productionRatioExpenseRow)toRows[idx].Row;
                if (ratioRow.planAmt == 0) continue;

                newRatioRow = toTbl.Add(ratioRow.productCode, ratioRow.taskCode, ratioRow.accountNo);
                newRatioRow.productionAmt = Math.Round(ratioRow.planAmt / totalAmt * row.amt, Settings.sysPrecisionAmt);
                remainOutAmt -= newRatioRow.productionAmt;
                lastRatioRow = newRatioRow;
            }
            if (lastRatioRow != null)
            {
                lastRatioRow.productionAmt += remainOutAmt;
            }
            return true;
        }
        public new ProductionRatioExpense Copy()
        {
            ProductionRatioExpense newData = new ProductionRatioExpense();
            tempDS.productionRatioExpenseDataTable tbl = (tempDS.productionRatioExpenseDataTable)base.Copy();
            for (int idx = 0; idx < tbl.Count; idx++) newData.ImportRow(tbl[idx]);
            return newData;
        }
    }

    public class ProductionCost
    {
        public ProductionData myProductionData = new ProductionData();
        public ProductionRatioMaterial myProductionRatioMaterial = new ProductionRatioMaterial();
        public ProductionRatioExpense myProductionRatioExpense = new ProductionRatioExpense();

        public ProductionErrors myErrors = new ProductionErrors();
      
        public void Reset()
        {
            this.myErrors.Clear();

            this.myProductionData.Clear();
            this.myProductionRatioMaterial.Clear();
            this.myProductionRatioExpense.Clear();
        }

        //Change to itemCode used in production
        private string ItemCodeInProduction(string itemCode)
        {
            data.masterDS.ItemRow itemRow = DataAccess.DataCache.FindITEM(itemCode);
            return (itemRow.IsprodItemCodeNull() ? itemCode : itemRow.prodItemCode);
        }

        // Calculate item qty that need to produce planned products
        private void AddToProduction_ProductPlan(tempDS.productionPlanDataTable dataTbl)
        {
            DataRowView[] foundRows;
            tempDS.productionRatioMaterialRow ratioMaterialRow;
            tempDS.productionRatioExpenseRow ratioExpenseRow;
            tempDS.productionDataRow productionDataRow;
            string productCode;
            for (int idx1 = 0; idx1 < dataTbl.Count; idx1++)
            {
                //Change to item code that used in production
                productCode = ItemCodeInProduction(dataTbl[idx1].itemCode);

                //Add to [planQty] in this.myProdItemTaskTbl
                productionDataRow = this.myProductionData.Add(productCode, dataTbl[idx1].taskCode);
                productionDataRow.planQty += dataTbl[idx1].planQty;
                productionDataRow.produceQty += dataTbl[idx1].produceQty;
                productionDataRow.completePerc = (productionDataRow.planQty == 0 ? 1 : productionDataRow.produceQty / productionDataRow.planQty) * 100;
                productionDataRow.completePerc = Math.Round(productionDataRow.completePerc,Settings.sysPrecisionPerc);

                //=========================//
                //  Material Planned Ratio //
                //=========================//
                this.myProductionRatioMaterial.LoadRatio(productCode, dataTbl[idx1].taskCode);
                foundRows = this.myProductionRatioMaterial.Find(productCode, dataTbl[idx1].taskCode);
                if (foundRows.Length == 0)
                {
                    this.myErrors.Add_TaskItem(ProductionErrors.Codes.ProductTaskNotExisted, dataTbl[idx1].taskCode, productCode);
                    continue;
                }
                for (int idx2 = 0; idx2 < foundRows.Length; idx2++)
                {
                    ratioMaterialRow = (tempDS.productionRatioMaterialRow)foundRows[idx2].Row;
                    ratioMaterialRow.planQty += dataTbl[idx1].planQty * ratioMaterialRow.planRatio;
                }
                //========================//
                // Expense Planned Ratio  //
                //========================//
                this.myProductionRatioExpense.LoadRatio(productCode, dataTbl[idx1].taskCode);
                foundRows = this.myProductionRatioExpense.Find (productCode, dataTbl[idx1].taskCode);
                if (foundRows.Length == 0)
                {
                    this.myErrors.Add_TaskItem(ProductionErrors.Codes.ProductTaskNotExisted, dataTbl[idx1].taskCode, productCode);
                    continue;
                }
                for (int idx2 = 0; idx2 < foundRows.Length; idx2++)
                {
                    ratioExpenseRow = (tempDS.productionRatioExpenseRow)foundRows[idx2].Row;
                    ratioExpenseRow.planAmt += dataTbl[idx1].planQty * ratioExpenseRow.planRatio;
                }               
            }
        }

        //=====================================
        //   Add material to production data
        //=====================================
        private void AddToProduction_Material(Material materialTbl)
        {
            tempDS.productionRatioMaterialRow ratioRow;
            
            ProductionRatioMaterial distReturnTbl = new ProductionRatioMaterial();
            DataRowView[] foundRows;

            //====================================================================
            // this.myProductionRatioMaterial keep the production data and it can 
            // be changed during the process. So we need to create a new one that 
            // keep the "original" ratio data that used for distribution
            //====================================================================
            ProductionRatioMaterial distProductionRatio = this.myProductionRatioMaterial.Copy();
            DataView viewByItemCode = new DataView(distProductionRatio);
            viewByItemCode.Sort = distProductionRatio.itemCodeColumn.ColumnName;
            for (int idx = 0; idx < materialTbl.Count; idx++)
            {
                if (materialTbl[idx].qty == 0 && materialTbl[idx].amt == 0) continue;

                distReturnTbl.Clear();
                //===========================================
                //  Know the product  : add to that product
                //===========================================
                if (materialTbl[idx].toProductCode.Trim() != "" && materialTbl[idx].toTaskCode.Trim() != "")
                {
                    ratioRow = distReturnTbl.Add(materialTbl[idx].itemCode, materialTbl[idx].toProductCode, materialTbl[idx].toTaskCode);
                    ratioRow.productionQty += materialTbl[idx].qty;
                    ratioRow.productionAmt += materialTbl[idx].amt;
                    //Estimate complete
                    EstimateComplete(materialTbl[idx], distReturnTbl);
                    continue;
                }
                //==========================================================
                // DONOT know the product : distribute to products in ratio
                //==========================================================
                foundRows = viewByItemCode.FindRows(materialTbl[idx].itemCode);
                // In production ratio, distribute it to specific ratios
                if (foundRows.Length != 0)
                {
                    if (distProductionRatio.Distribute(materialTbl[idx], foundRows, distReturnTbl) == false)
                         this.myErrors.Add_Item(ProductionErrors.Codes.DistributionFailed, materialTbl[idx].itemCode);
                    else EstimateComplete(materialTbl[idx], distReturnTbl);
                    continue;
                }
                // NOT in production ratio, distributed to all ratios  
                if (this.myProductionData.DistributeMaterial(materialTbl[idx], distReturnTbl) == false)
                    this.myErrors.Add_Item(ProductionErrors.Codes.DistributionFailed, materialTbl[idx].itemCode);
                else EstimateComplete(materialTbl[idx], distReturnTbl);
            }

            //=====================================
            //  Calculate some metrics for testing
            //=====================================
            // Production ratio and variant
            tempDS.productionDataRow productionDataRow;
            for (int idx = 0; idx < this.myProductionRatioMaterial.Count; idx++)
            {
                if (this.myProductionRatioMaterial[idx].productionQty == 0) continue;

                productionDataRow = this.myProductionData.Find(this.myProductionRatioMaterial[idx].productCode,this.myProductionRatioMaterial[idx].taskCode);
                if (productionDataRow == null)
                {
                    this.myErrors.Add_TaskItem(ProductionErrors.Codes.NotInProductPlan, this.myProductionRatioMaterial[idx].taskCode, this.myProductionRatioMaterial[idx].productCode);
                    continue;
                }
                if (productionDataRow.planQty == 0)
                {
                    if (this.myProductionRatioMaterial[idx].productionQty != 0)
                        this.myErrors.Add_Item(ProductionErrors.Codes.ProductPlanQtyInvalid, this.myProductionRatioMaterial[idx].productCode);
                    continue;
                }
                this.myProductionRatioMaterial[idx].productionRatio = Math.Round(this.myProductionRatioMaterial[idx].productionQty / productionDataRow.planQty, Settings.sysPrecisionRatio);
                if (this.myProductionRatioMaterial[idx].planRatio == 0)  this.myProductionRatioMaterial[idx].ratioVariantPerc = 1;
                else
                    this.myProductionRatioMaterial[idx].ratioVariantPerc =  (this.myProductionRatioMaterial[idx].productionRatio - this.myProductionRatioMaterial[idx].planRatio) / this.myProductionRatioMaterial[idx].planRatio;
                this.myProductionRatioMaterial[idx].ratioVariantPerc = Math.Round(this.myProductionRatioMaterial[idx].ratioVariantPerc * 100, Settings.sysPrecisionPerc);
            }
            // Material complete ratio
            for (int idx = 0; idx < materialTbl.Count; idx++)
            {
                if (materialTbl[idx].qty == 0) continue;
                materialTbl[idx].completePerc = Math.Round(100*materialTbl[idx].completeQty/materialTbl[idx].qty,Settings.sysPrecisionPerc);
            }
        }

        //=====================================
        //   Add expense to production data
        //=====================================
        private void AddToProduction_Expense(Expense expenseTbl)
        {
            tempDS.productionRatioExpenseRow ratioRow;

            ProductionRatioExpense distReturnTbl = new ProductionRatioExpense();
            DataRowView[] foundRows;

            //====================================================================
            // this.myProductionRatioExpense keep the production data and it can 
            // be changed during the process. So we need to create a new one that 
            // keep the "original" ratio data that used for distribution
            //====================================================================
            ProductionRatioExpense distProductionRatio = this.myProductionRatioExpense.Copy();
            DataView viewByItemCode = new DataView(distProductionRatio);
            viewByItemCode.Sort = distProductionRatio.accountNoColumn.ColumnName;
            for (int idx = 0; idx < expenseTbl.Count; idx++)
            {
                if (expenseTbl[idx].amt == 0) continue;

                distReturnTbl.Clear();
                //===========================================
                //  Know the product  : add to that product
                //===========================================
                if (expenseTbl[idx].toProductCode.Trim() != "" && expenseTbl[idx].toTaskCode.Trim() != "")
                {
                    ratioRow = distReturnTbl.Add(expenseTbl[idx].toProductCode, expenseTbl[idx].toTaskCode, expenseTbl[idx].accountNo);
                    ratioRow.productionAmt += expenseTbl[idx].amt;
                    //Estimate complete
                    EstimateComplete(expenseTbl[idx], distReturnTbl);
                    continue;
                }
                //==========================================================
                // DONOT know the product : distribute to products in ratio
                //==========================================================
                foundRows = viewByItemCode.FindRows(expenseTbl[idx].accountNo);
                // In production ratio, distribute it to specific ratios
                if (foundRows.Length != 0)
                {
                    if (distProductionRatio.Distribute(expenseTbl[idx], foundRows, distReturnTbl) == false)
                        this.myErrors.Add_Account(ProductionErrors.Codes.DistributionFailed, expenseTbl[idx].accountNo);
                    else EstimateComplete(expenseTbl[idx], distReturnTbl);
                    continue;
                }
                // NOT in production ratio, distributed to all ratios  
                if (this.myProductionData.DistributeExpense(expenseTbl[idx], distReturnTbl) == false)
                    this.myErrors.Add_Account(ProductionErrors.Codes.DistributionFailed, expenseTbl[idx].accountNo);
                else EstimateComplete(expenseTbl[idx], distReturnTbl);
            }

            //=====================================
            //  Calculate some metrics for testing
            //=====================================
            // Production ratio and variant
            tempDS.productionDataRow productionDataRow;
            for (int idx = 0; idx < this.myProductionRatioExpense.Count; idx++)
            {
                if (this.myProductionRatioExpense[idx].productionAmt == 0) continue;

                productionDataRow = this.myProductionData.Find(this.myProductionRatioExpense[idx].productCode, this.myProductionRatioExpense[idx].taskCode);
                if (productionDataRow == null)
                {
                    this.myErrors.Add_TaskItem(ProductionErrors.Codes.NotInProductPlan, this.myProductionRatioExpense[idx].taskCode, this.myProductionRatioExpense[idx].productCode);
                    continue;
                }
                if (productionDataRow.planQty == 0)
                {
                    if (this.myProductionRatioExpense[idx].productionAmt != 0)
                        this.myErrors.Add_Item(ProductionErrors.Codes.ProductPlanQtyInvalid, this.myProductionRatioExpense[idx].productCode);
                    continue;
                }
                this.myProductionRatioExpense[idx].productionRatio = Math.Round(this.myProductionRatioExpense[idx].productionAmt  / productionDataRow.planQty, Settings.sysPrecisionRatio);
                if (this.myProductionRatioExpense[idx].planRatio == 0) this.myProductionRatioExpense[idx].ratioVariantPerc = 1;
                else
                    this.myProductionRatioExpense[idx].ratioVariantPerc = (this.myProductionRatioExpense[idx].productionRatio - this.myProductionRatioExpense[idx].planRatio) / this.myProductionRatioExpense[idx].planRatio;
                this.myProductionRatioExpense[idx].ratioVariantPerc = Math.Round(this.myProductionRatioExpense[idx].ratioVariantPerc * 100, Settings.sysPrecisionPerc);
            }
            // Material complete ratio
            for (int idx = 0; idx < expenseTbl.Count; idx++)
            {
                if (expenseTbl[idx].amt == 0) continue;
                expenseTbl[idx].completePerc = Math.Round(100 * expenseTbl[idx].completeAmt / expenseTbl[idx].amt, Settings.sysPrecisionPerc);
            }
        }

        //==============================================================
        //  Estinate completeness and update to production data
        //==============================================================
        private void EstimateComplete(tempDS.materialRow materialRow, tempDS.productionRatioMaterialDataTable dataTbl)
        {
            ProductionData.EstimateResult estResult;
            tempDS.productionRatioMaterialRow ratioRow;
            for (int idx1 = 0; idx1 < dataTbl.Count; idx1++)
            {
                ratioRow = this.myProductionRatioMaterial.Add(dataTbl[idx1].itemCode, dataTbl[idx1].productCode, dataTbl[idx1].taskCode);
                ratioRow.productionQty += dataTbl[idx1].productionQty;
                ratioRow.productionAmt += dataTbl[idx1].productionAmt;

                estResult = this.myProductionData.EstimateComplete_Material(ratioRow.productCode, ratioRow.taskCode, dataTbl[idx1].productionQty, dataTbl[idx1].productionAmt, true);
                if (estResult != null)
                {
                    ratioRow.completeQty += estResult.completeQty;
                    ratioRow.completeAmt += estResult.completeAmt;

                    materialRow.completeQty += estResult.completeQty;
                    materialRow.completeAmt += estResult.completeAmt;
                    continue;
                }
            }
        }
        private void EstimateComplete(tempDS.expenseRow expenseRow, tempDS.productionRatioExpenseDataTable dataTbl)
        {
            ProductionData.EstimateResult estResult;
            tempDS.productionRatioExpenseRow ratioRow;
            for (int idx1 = 0; idx1 < dataTbl.Count; idx1++)
            {
                ratioRow = this.myProductionRatioExpense.Add(dataTbl[idx1].productCode, dataTbl[idx1].taskCode, dataTbl[idx1].accountNo);
                ratioRow.productionAmt += dataTbl[idx1].productionAmt;

                estResult = this.myProductionData.EstimateComplete_Expense(ratioRow.productCode, ratioRow.taskCode,dataTbl[idx1].productionAmt, true);
                if (estResult != null)
                {
                    ratioRow.completeAmt += estResult.completeAmt;
                    expenseRow.completeAmt += estResult.completeAmt;
                    continue;
                }
            }
        }
        
        public void Estimate(tempDS.productionPlanDataTable prodPlanTbl, Material materialTbl, Expense expenseTbl)
        {
            AddToProduction_ProductPlan(prodPlanTbl);
            this.myProductionData.CheckError(this.myErrors);

            materialTbl.Validate(prodPlanTbl);
            AddToProduction_Material(materialTbl);
            AddToProduction_Expense(expenseTbl);
        }

        //================================================================================================
        //Ensure Incomplete amount equals the amount in  myProductionMaterialTbl + myProductionExpenseTbl)
        //================================================================================================
        public bool AdjustIncompleteAmt(Material materialTbl, Expense expenseTbl, decimal accountIncompleteAmt)
        {
            decimal totalWeight = 0, costIncompleteAmt = 0;
            for (int idx = 0; idx < materialTbl.Count; idx++)
            {
                totalWeight += materialTbl[idx].amt - materialTbl[idx].completeAmt;
                costIncompleteAmt += materialTbl[idx].amt - materialTbl[idx].completeAmt;
            }
            for (int idx = 0; idx < expenseTbl.Count; idx++)
            {
                costIncompleteAmt += expenseTbl[idx].amt - expenseTbl[idx].completeAmt;
            }
            if (totalWeight == 0)
            {
                if (costIncompleteAmt != 0)
                {
                    this.myErrors.Add(ProductionErrors.Codes.IncompleteAdjustFailed);
                    return false;
                }
                return true;
            }
            tempDS.materialRow lastRow = null;
            decimal diffAmt, remainAmt, amt;
            diffAmt = accountIncompleteAmt - costIncompleteAmt;
            remainAmt = diffAmt;
            for (int idx = 0; idx < materialTbl.Count; idx++)
            {
                amt = Math.Round(diffAmt * (materialTbl[idx].amt - materialTbl[idx].completeAmt) / totalWeight, Settings.sysPrecisionAmt);
                if (amt != 0)
                {
                    materialTbl[idx].differentialAmt += amt;
                    remainAmt -= amt;
                    lastRow = materialTbl[idx];
                }
            }
            if (remainAmt != 0 && lastRow != null)
            {
                lastRow.differentialAmt += remainAmt;
            }
            return true;
        }

        public void SaveProductionData(DateTime onDate)
        {
            this.myProductionData.SaveData(onDate);
            this.myProductionRatioMaterial.SaveData(onDate);
            this.myProductionRatioExpense.SaveData(onDate);
        }

        public decimal GetProductionAmount(string productCode, string taskCode, decimal qty, bool withUpdate)
        {
            decimal amt;
            tempDS.productionDataRow productionRow;
            productionRow = this.myProductionData.Find(productCode,taskCode);
            if (productionRow == null)
            {
                return common.Consts.constNullDecimal;
            }
            amt = Math.Round(qty * (productionRow.produceMaterialAmt + productionRow.produceExpenseAmt) / productionRow.produceQty, production.Settings.sysPrecisionAmt);
            if (productionRow.distProduceQty + qty == productionRow.produceQty)
            {
                amt = productionRow.produceMaterialAmt + productionRow.produceExpenseAmt - productionRow.distProduceAmt;
            }
            if (withUpdate)
            {
                productionRow.distProduceQty += qty;
                productionRow.distProduceAmt += amt;
            }
            return amt;
        }
    }
}


           